/**
 * @file structures.h
 * @author Kuba
 *
 * Pomocnicze struktury i metody globalne potrzebne w pozostałych klasach projektu.
 */

#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include "MacCollector.h"

#ifndef STRUCTURES_H_
#define STRUCTURES_H_

#define stshort(sval,addr) ( *( (u_short *)(addr))=htons(sval))
#define MAXDATA 	512	/* Maksymalny rozmiar bloku danych  */
#define	 ERR_UNDEF	0	/* not defined, see error message */
#define	 ERR_NOFILE	1	/* File not found */
#define	 ERR_ACCESS	2	/* Access violation */
#define	 ERR_NOSPACE	3	/* Disk full or allocation exceeded */
#define	 ERR_BADOP	4	/* Illegal tftp operation */
#define	 ERR_BADID	5	/* Unknown TID (port#) */
#define	 ERR_FILE	6	/* File already exists */
#define	 ERR_NOUSER	7	/* No such user */

/**
 * Struktura pomocnicza niezbędna do przkazania argumentów wywołania do metody run() klasy ConfigChanger ze względu na zastosowanie wątków.
 */
struct ConfigChangerArguments
    {
    int port;
    char password[20];
    };

/**
 * Struktura pomocnicza reprezentująca adres sprzętowy MAC w formacie "xx:xx:xx:xx:xx:xx".
 * "*" reprezentuje dowolny adres MAC
 */
struct MAC
    {
    MAC()
	{
	}
    ;
    MAC(std::string mac_) :
	mac(mac_)
	{
	}
    ;
    bool operator==(const MAC& right) const
	{
	return !(this->mac.compare(right.mac));
	}
    ;

    std::string mac;
    };


/**
 * Struktura pomocnicza reprezentująca adres IP w formacie "x.x.x.x".
 * "*" reprezentuje dowolny adres IP
 */
struct IP
{
    IP()
	{
    	type = false;
    	all = '0';
	};

    IP(const IP &ip)
	{
    	type = ip.type;
    	all = ip.all;
    	ipv4[0] = ip.ipv4[0];
    	ipv4[1] = ip.ipv4[1];
    	ipv4[2] = ip.ipv4[2];
    	ipv4[3] = ip.ipv4[3];
	};

    IP(std::string ip_)
	{
    	char temp[4];
    	int k = 0;

    	if(ip_[0] == '*')
    	{
    		all = '*';
    		type = false;
    	}
    	else
    	{
    		all = '0';
    		type = true;
			for(int i = 0, j=0; i<ip_.length(); i++)
			{
				if(ip_[i] == '.')
				{
					ipv4[j] = atoi(temp);
					j++;
					i++;
					k = 0;
					for(int ii = 0; ii < 4; ii++)
						temp[ii] = 0;

				}
				temp[k] = ip_[i];
				k++;
			}
			ipv4[3] = atoi(temp);
    	}


	};
    bool operator==(const IP& right) const
	{
    	if(type != right.type)
    		return false;
    	if(type)
    	{
			if(ipv4[0] != right.ipv4[0])
				return false;
			else if(ipv4[1] != right.ipv4[1])
				return false;
			else if(ipv4[2] != right.ipv4[2])
				return false;
			else if(ipv4[3] != right.ipv4[3])
				return false;
	    	return true;
    	}
    	else
    	{
    		if(all != '*' || right.all != '*')
    			return false;
    		return true;
    	}

	};

    bool operator<(const IP & ip) const
    {
    	if(type != ip.type)
    		return false;
    	if(*this == ip)
    		return false;
    	if(ipv4[0]> ip.ipv4[0])
    		return false;
    	if(ipv4[1]> ip.ipv4[1])
    		return false;
    	if(ipv4[2]> ip.ipv4[2])
    		return false;
    	if(ipv4[3]> ip.ipv4[3])
    		return false;
    	return true;
    }

    friend std::ostream & operator<<(std::ostream & os, const IP & ip_)
    {
    	if(ip_.type)
    		os<<ip_.ipv4[0]<<"."<<ip_.ipv4[1]<<"."<<ip_.ipv4[2]<<"."<<ip_.ipv4[3];
    	else
    		os<<ip_.all;
    	return os;
    }

private:
	/**
	 * Określa czy IP jest podane jako liczba czy jako specjalne IP '*' oznaczające wszyskie możliwe IP
	 */
	bool type;
public:
	/**
	 * Znak określający wszystkie IP
	 */
	char all;
	/**
	 * Tablica z wartościami IP
	 */
	int ipv4[4];
};

/**
 * Funkcja pomocnicza obliczająca długość łańcucha znaków zakończonego znakiem końca "0".
 * @param buffer_ - łancuch znaków.
 */
static int bufferSize(char *buffer_)
    {
    int n = 0;
    while (*buffer_ != '\0')
	{
	n++;
	buffer_++;
	}
    return n;

    }
;

static char checkMode(char *mode)
    {
    char* netascii = "netascii";
    char* octet = "octet";
    int count = 0;
    int size = 0;

    for (int i = 0; netascii[i] != '\0'; i++)
	{
	if (netascii[i] == mode[i])
	    count++;
	}
    size = bufferSize(netascii);
    if (count == size /*&& mode[size + 1] == netascii[size + 1]*/)
	return 'a';

    count = 0;
    size = 0;

    for (int i = 0; octet[i] != '\0'; i++)
	{
	if (octet[i] == mode[i])
	    count++;
	}
    size = bufferSize(octet);
    if (count == size /*&& mode[size + 1] == octet[size + 1]*/)
	return 'b';
    else
	return 'o';
    }

/**
 * Struktura reprezentująca paczkę danych wysyłanych pomiędzy serwerem a klientami.
 */
struct Package
    {
    /**
     * Stworzenie pustej paczki.
     */
    Package()
	{
	bufferSize = 0;
	}
    ;
    /**
     * Zapisanie do struktury bufora znaków odebranego od klienta.
     * @param buffer_ - bufor danych odebrany od klienta.
     */
    Package(char *buffer)
	{
	char filename_[256];
	char errormsg_[256];
	char mode_[256];
	char data_[512];
	char tmp[2];
	int i, j;
	switch (buffer[1])
	    {
	case 1:
	    for (i = 2, j = 0; buffer[i] != '\0'; i++, j++)
		filename_[j] = buffer[i];
	    filename_[j] = '\0';
	    for (++i, j = 0; buffer[i] != '\0'; i++, j++)
		mode_[j] = buffer[i];
	    mode_[j] = '\0';
	    setPRQ(filename_, mode_);
	    break;
	case 2:
	    for (i = 2, j = 0; buffer[i] != '\0'; i++, j++)
		filename_[j] = buffer[i];
	    filename_[j] = '\0';
	    for (++i, j = 0; buffer[i] != '\0'; i++, j++)
		mode_[j] = buffer[i];
	    mode_[j] = '\0';
	    setWRQ(filename_, mode_);
	    break;
	case 3:
	    tmp[0] = buffer[2];
	    tmp[1] = buffer[3];
	    for (i = 4, j = 0; buffer[i] != '\0' && j != 512; i++, j++)
		data_[j] = buffer[i];
	    data_[j] = '\0';
	    setData(ntohs(*(u_short*) tmp), data_, 512);
	    //  setData(ntohs(*(u_short*) tmp), data_);
	    break;
	case 4:
	    tmp[0] = buffer[2];
	    tmp[1] = buffer[3];
	    setACK(ntohs(*(u_short*) tmp));
	    break;
	case 5:
	    tmp[0] = buffer[2];
	    tmp[1] = buffer[3];
	    for (i = 4, j = 0; buffer[i] != '\0'; i++, j++)
		errormsg_[j] = buffer[i];
	    errormsg_[j] = '\0';
	    setError(ntohs(*(u_short*) tmp), errormsg_);
	    break;
	    }
	}
    ;
    /**
     * Stworzenie pakietu zawierającego komunikat typu PRQ.
     * @param filename_ - nazwa pliku.
     * @param mode_ - tryb przesyłąnia danych.
     */
    void setPRQ(char* filename_, char* mode_)
	{
	opcode[0] = 0;
	opcode[1] = 1;
	strcpy(filename, filename_);
	strcpy(mode, mode_);
	}
    ;
    /**
     * Stworzenie pakietu zawierającego komunikat typu WRQ.
     * @param filename_ - nazwa pliku.
     * @param mode_ - tryb przesyłąnia danych.
     */
    void setWRQ(char* filename_, char* mode_)
	{
	opcode[0] = 0;
	opcode[1] = 2;
	strcpy(filename, filename_);
	strcpy(mode, mode_);
	}
    ;
    /**
     * Stworzenie pakietu zawierającego pakiet danych.
     * @param block_ - numer bloku danych.
     * @param data_ - bufor z danymi.
     * @param size_ - rozmiar danych.
     */
    void setData(int block_, char* data_, int size_)
	{
	opcode[0] = 0;
	opcode[1] = 3;
	block = block_;
	copyChar(data, data_, size_);
	bufferSize = size_;
	//strcpy(data, data_);
	}
    ;
    /**
     * Stworzenie pakietu zawierającego komunikat typu ACK.
     * @param block_ - numer bloku danych.
     */
    void setACK(int block_)
	{
	opcode[0] = 0;
	opcode[1] = 4;
	block = block_;
	}
    ;
    /**
     * Stworzenie pakietu zawierającego pakiet typu Error.
     * @param errorCode_ - numer błędu.
     * @param errorMsg_ - ewentualny opis blędu.
     */
    void setError(short int errorCode_, char* errorMsg_)
	{
	opcode[0] = 0;
	opcode[1] = 5;
	errorCode = errorCode_;
	strcpy(errorMsg, errorMsg_);
	}
    ;
    /**
     * Meotda wyświetlająca szczegółowe informacje o zawartości struktury.
     */
    void print()
	{
	switch (opcode[1])
	    {
	case 1:
	    std::cout << "Package type: RRQ" << std::endl;
	    std::cout << "Filename: " << filename << std::endl;
	    std::cout << "Mode: " << mode << std::endl;
	    break;
	case 2:
	    std::cout << "Package type: WRQ" << std::endl;
	    std::cout << "Filename: " << filename << std::endl;
	    std::cout << "Mode: " << mode << std::endl;
	    break;
	case 3:
	    std::cout << "P ackage type: DATA" << std::endl;
	    std::cout << "Block: " << block << std::endl;
	    std::cout << "Data: " << data << std::endl;
	    break;
	case 4:
	    std::cout << "Package type: ACK" << std::endl;
	    std::cout << "Block: " << block << std::endl;
	    break;
	case 5:
	    std::cout << "Package type: ERROR" << std::endl;
	    std::cout << "Code: " << errorCode << std::endl;
	    std::cout << "Description: " << errorMsg << std::endl;
	    break;
	default:
	    std::cout << "Package type: Unknown" << std::endl;
	    }
	}
    /**
     * Metoda zwracająca wielkość bufora odpowiadającego pakietowi.
     */
    int getBufferSize()
	{
	return bufferSize;
	}
    /**
     * Stworzenie bufora z danymi do wysłania, na podstawie danych ze struktury.
     * @param buffer_ - bufor danych do zapisu informacji ze struktury.
     */
    void makeBuffer(char* buffer_)
	{
	buffer_[0] = opcode[0];
	buffer_[1] = opcode[1];
	int i, j;
	switch (opcode[1])
	    {
	case 1:
	    for (i = 0, j = 2; filename[i] != '\0'; i++, j++)
		buffer_[j] = filename[i];
	    buffer_[j] = '\0';
	    for (i = 0, ++j; mode[i] != '\0'; i++, j++)
		buffer_[j] = mode[i];
	    buffer_[j] = '\0';
	    bufferSize = j + 1;
	    break;
	case 2:
	    for (i = 0, j = 2; filename[i] != '\0'; i++, j++)
		buffer_[j] = filename[i];
	    buffer_[j] = '\0';
	    for (i = 0, ++j; mode[i] != '\0'; i++, j++)
		buffer_[j] = mode[i];
	    buffer_[j] = '\0';
	    bufferSize = j + 1;
	    break;
	case 3:
	    stshort(block,buffer_+2);
	    //for (i = 0, j = 4; data[i] != '\0' && i != 512; i++, j++)
	    for (i = 0, j = 4; i < bufferSize; i++, j++)
		buffer_[j] = data[i];
	    	    bufferSize = j;
	    break;
	case 4:
	    stshort(block,buffer_+2);
	    bufferSize = 4;
	    break;
	case 5:
	    stshort(errorCode,buffer_+2);
	    for (i = 0, j = 4; errorMsg[i] != '\0'; i++, j++)
		buffer_[j] = errorMsg[i];
	    buffer_[j] = '\0';
	    bufferSize = j + 1;
	    break;
	    }
	}
    /**
     * Numer pakietu reprezentujący jego typ.
     */
    char opcode[2];
    /**
     * Nazwa przesyłanego pliku.
     */
    char filename[256];
    /**
     * Nazwa trybu przesyłania pliku.
     */
    char mode[256];
    /**
     * Numer bloku który jest przesyłany.
     */
    short int block;
    /**
     * Pakiet danych zawierająca dane pliku.
     */
    char data[512];
    /**
     * Numer błędu.
     */
    short int errorCode;
    /**
     * Opis blędu.
     */
    char errorMsg[256];
    /**
     * Rozmiar bufora.
     */
    int bufferSize;

private:
    /**
     * Metoda kopiująca określoną ilość danych z jednego ciągu do drugiego
     * @param dst_ - skopiowane dane.
     * @param src_ - żródło danych.
     * @param size_ - rozmiar danych do skopiowania.
     */
    void copyChar(char* dst, char* src, int size_)
	{
	for (int i = 0; i < size_; i++)
	    {
	    *dst = *src;
	    dst++;
	    src++;
	    }
	}
    };

class ClientControllerArguments
    {
public:
    //	string (*func)(string);
    class MacCollector *ptr;
    Package package;
    //	MacCollector *ptr;
    int socket;
    struct sockaddr_in newClientName;
    };

#endif /* STRUCTURES_H_ */
